/*--------------------------------------------------------------------------
 * ginblock.h
 *	  存储在 GIN 索引块中的结构细节
 *
 *	Copyright (c) 2006-2022, PostgreSQL Global Development Group
 *
 *	src/include/access/ginblock.h
 *--------------------------------------------------------------------------
 */
#ifndef GINBLOCK_H
#define GINBLOCK_H

#include "access/transam.h"
#include "storage/block.h"
#include "storage/bufpage.h"
#include "storage/itemptr.h"
#include "storage/off.h"

/*
 * 反向索引页面中的页面不透明数据。
 *
 * 注意：GIN不像其他索引类型那样包含页面 ID 字段。
 * 这是可以的，因为不透明数据只有 8 字节，因此可以通过大小可靠地区分。
 * 如果大小有增加，请重新审视这一点。
 * 进一步注意：自 9.2 以来，SP-GiST 也使用 8 字节特殊空间，9.5 的 BRIN 也是如此。
 * 只要 GIN 没有使用其标志字中的所有高位，这仍然是可以的，
 * 因为这样标志字无法与 SP-GiST 和 BRIN 使用的页面 ID 匹配。
 */
typedef struct GinPageOpaqueData
{
	BlockNumber rightlink;		/* 如果有下一个页面 */
	OffsetNumber maxoff;		/* GIN_DATA 和 ~GIN_LEAF 页上的 PostingItems 数量。
 * 在 GIN_LIST 页面上，堆元组的数量。
 */
	uint16		flags;			/* 见下面的位定义 */
} GinPageOpaqueData;

typedef GinPageOpaqueData *GinPageOpaque;

#define GIN_DATA		  (1 << 0)
#define GIN_LEAF		  (1 << 1)
#define GIN_DELETED		  (1 << 2)
#define GIN_META		  (1 << 3)
#define GIN_LIST		  (1 << 4)
#define GIN_LIST_FULLROW  (1 << 5)	/* 仅在 GIN_LIST 页面上有意义 */
#define GIN_INCOMPLETE_SPLIT (1 << 6)	/* 页面已拆分，但父页面未更新 */
#define GIN_COMPRESSED	  (1 << 7)

/* 固定位置页面的页面编号 */
#define GIN_METAPAGE_BLKNO	(0)
#define GIN_ROOT_BLKNO		(1)

typedef struct GinMetaPageData
{
	/*
	 * 指向待处理列表的头和尾的指针，该列表由 GIN_LIST 页面组成。
	 * 这些存储尚未移动到常规 GIN 结构中的快速插入条目。
	 */
	BlockNumber head;
	BlockNumber tail;

	/*
	 * 待处理列表尾页面中的空闲空间（以字节为单位）。
	 */
	uint32		tailFreeSize;

	/*
	 * 我们存储待处理列表中的页面数量和堆元组数量。
	 */
	BlockNumber nPendingPages;
	int64		nPendingHeapTuples;

	/*
	 * 供规划器使用的统计信息（截至最后一次 VACUUM 准确）
	 */
	BlockNumber nTotalPages;
	BlockNumber nEntryPages;
	BlockNumber nDataPages;
	int64		nEntries;

	/*
	 * GIN 版本号（理想情况下应该在前面，但现在太晚了。不要移动它！）
	 *
	 * 目前为 2（针对 9.4 或更高版本初始化的索引）
	 *
	 * 版本 1（在版本 9.1、9.2 或 9.3 中初始化的索引）是
	 * 兼容的，但可能包含未压缩的发布树（叶子）页面和发布列表。
	 * 在修改时会转换为压缩格式。
	 *
	 * 版本 0（在 9.0 或之前初始化的索引）是兼容的，但可能缺少空条目，
	 * 包括空键和占位符。
	 * 拒绝对这类索引进行全索引扫描尝试。
	 */
	int32		ginVersion;
} GinMetaPageData;

#define GIN_CURRENT_VERSION		2

#define GinPageGetMeta(p) \
	((GinMetaPageData *) PageGetContents(p))

/*
 * 访问 GIN 索引页面不透明数据的宏
 */
#define GinPageGetOpaque(page) ( (GinPageOpaque) PageGetSpecialPointer(page) )

#define GinPageIsLeaf(page)    ( (GinPageGetOpaque(page)->flags & GIN_LEAF) != 0 )
#define GinPageSetLeaf(page)   ( GinPageGetOpaque(page)->flags |= GIN_LEAF )
#define GinPageSetNonLeaf(page)    ( GinPageGetOpaque(page)->flags &= ~GIN_LEAF )
#define GinPageIsData(page)    ( (GinPageGetOpaque(page)->flags & GIN_DATA) != 0 )
#define GinPageSetData(page)   ( GinPageGetOpaque(page)->flags |= GIN_DATA )
#define GinPageIsList(page)    ( (GinPageGetOpaque(page)->flags & GIN_LIST) != 0 )
#define GinPageSetList(page)   ( GinPageGetOpaque(page)->flags |= GIN_LIST )
#define GinPageHasFullRow(page)    ( (GinPageGetOpaque(page)->flags & GIN_LIST_FULLROW) != 0 )
#define GinPageSetFullRow(page)   ( GinPageGetOpaque(page)->flags |= GIN_LIST_FULLROW )
#define GinPageIsCompressed(page)	 ( (GinPageGetOpaque(page)->flags & GIN_COMPRESSED) != 0 )
#define GinPageSetCompressed(page)	 ( GinPageGetOpaque(page)->flags |= GIN_COMPRESSED )

#define GinPageIsDeleted(page) ( (GinPageGetOpaque(page)->flags & GIN_DELETED) != 0 )
#define GinPageSetDeleted(page)    ( GinPageGetOpaque(page)->flags |= GIN_DELETED)
#define GinPageSetNonDeleted(page) ( GinPageGetOpaque(page)->flags &= ~GIN_DELETED)
#define GinPageIsIncompleteSplit(page) ( (GinPageGetOpaque(page)->flags & GIN_INCOMPLETE_SPLIT) != 0 )

#define GinPageRightMost(page) ( GinPageGetOpaque(page)->rightlink == InvalidBlockNumber)

/*
 * 我们应只在每个事务启动之前才能回收已删除页面一次
 * 其删除尚未结束。
 */
#define GinPageGetDeleteXid(page) ( ((PageHeader) (page))->pd_prune_xid )
#define GinPageSetDeleteXid(page, xid) ( ((PageHeader) (page))->pd_prune_xid = xid)
extern bool GinPageIsRecyclable(Page page);

/*
 * 我们使用自己的 ItemPointerGet(BlockNumber|OffsetNumber)
 * 以避免断言，因为有时 ip_posid 不是“有效的”
 */
#define GinItemPointerGetBlockNumber(pointer) \
	(ItemPointerGetBlockNumberNoCheck(pointer))

#define GinItemPointerGetOffsetNumber(pointer) \
	(ItemPointerGetOffsetNumberNoCheck(pointer))

#define GinItemPointerSetBlockNumber(pointer, blkno) \
	(ItemPointerSetBlockNumber((pointer), (blkno)))

#define GinItemPointerSetOffsetNumber(pointer, offnum) \
	(ItemPointerSetOffsetNumber((pointer), (offnum)))


/*
 * GIN 搜索逻辑所需的特殊情况项目指针值。
 *	MIN：排序小于任何有效项目指针
 *	MAX：排序大于任何有效项目指针
 *	LOSSY PAGE：表示整个堆页面，对该页面的正常项目指针之后进行排序
 * 请注意，这些都可以与“无效”项目指针区分开
 * （即 InvalidBlockNumber/0）以及所有正常项目指针
 * （其项目编号在 1..MaxHeapTuplesPerPage 范围内）。
 */
#define ItemPointerSetMin(p)  \
	ItemPointerSet((p), (BlockNumber)0, (OffsetNumber)0)
#define ItemPointerIsMin(p)  \
	(GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0 && \
	 GinItemPointerGetBlockNumber(p) == (BlockNumber)0)
#define ItemPointerSetMax(p)  \
	ItemPointerSet((p), InvalidBlockNumber, (OffsetNumber)0xffff)
#define ItemPointerSetLossyPage(p, b)  \
	ItemPointerSet((p), (b), (OffsetNumber)0xffff)
#define ItemPointerIsLossyPage(p)  \
	(GinItemPointerGetOffsetNumber(p) == (OffsetNumber)0xffff && \
	 GinItemPointerGetBlockNumber(p) != InvalidBlockNumber)

/*
 * 非叶子发布树页面中的发布项
 */
typedef struct
{
	/* 我们使用 BlockIdData 而不是 BlockNumber 以避免填充空间浪费 */
	BlockIdData child_blkno;
	ItemPointerData key;
} PostingItem;

#define PostingItemGetBlockNumber(pointer) \
	BlockIdGetBlockNumber(&(pointer)->child_blkno)

#define PostingItemSetBlockNumber(pointer, blockNumber) \
	BlockIdSet(&((pointer)->child_blkno), (blockNumber))

/*
 * 类别代码用于区分占位符空值和普通 NULL 键。
 *
 * 前两个代码值选择与通常使用的 bool isNull 标志兼容。
 * 然而，在 bool 和 GinNullCategory 之间进行转换是有风险的，
 * 因为可能存在不同的位模式和类型大小，所以不再进行此操作。
 *
 * GIN_CAT_EMPTY_QUERY 从不存储在索引中；并注意，它
 * 被选为在正常键值之前而不是之后排序。
 */
typedef signed char GinNullCategory;

#define GIN_CAT_NORM_KEY		0	/* 正常的非空键值 */
#define GIN_CAT_NULL_KEY		1	/* 空键值 */
#define GIN_CAT_EMPTY_ITEM		2	/* 零键项目的占位符 */
#define GIN_CAT_NULL_ITEM		3	/* 空项目的占位符 */
#define GIN_CAT_EMPTY_QUERY		(-1)	/* 全扫描查询的占位符 */

/*
 * 进入元组中的空类别字节的访问宏
 */
#define GinCategoryOffset(itup,ginstate) \
	(IndexInfoFindDataOffset((itup)->t_info) + \
	 ((ginstate)->oneCol ? 0 : sizeof(int16)))
#define GinGetNullCategory(itup,ginstate) \
	(*((GinNullCategory *) ((char*)(itup) + GinCategoryOffset(itup,ginstate))))
#define GinSetNullCategory(itup,ginstate,c) \
	(*((GinNullCategory *) ((char*)(itup) + GinCategoryOffset(itup,ginstate))) = (c))

/*
 * 叶子页面条目元组的访问宏（请参见 README 中的讨论） 
 */
#define GinGetNPosting(itup)	GinItemPointerGetOffsetNumber(&(itup)->t_tid)
#define GinSetNPosting(itup,n)	ItemPointerSetOffsetNumber(&(itup)->t_tid,n)
#define GIN_TREE_POSTING		((OffsetNumber)0xffff)
#define GinIsPostingTree(itup)	(GinGetNPosting(itup) == GIN_TREE_POSTING)
#define GinSetPostingTree(itup, blkno)	( GinSetNPosting((itup),GIN_TREE_POSTING), ItemPointerSetBlockNumber(&(itup)->t_tid, blkno) )
#define GinGetPostingTree(itup) GinItemPointerGetBlockNumber(&(itup)->t_tid)

#define GIN_ITUP_COMPRESSED		(1U << 31)
#define GinGetPostingOffset(itup)	(GinItemPointerGetBlockNumber(&(itup)->t_tid) & (~GIN_ITUP_COMPRESSED))
#define GinSetPostingOffset(itup,n) ItemPointerSetBlockNumber(&(itup)->t_tid,(n)|GIN_ITUP_COMPRESSED)
#define GinGetPosting(itup)			((Pointer) ((char*)(itup) + GinGetPostingOffset(itup)))
#define GinItupIsCompressed(itup)	((GinItemPointerGetBlockNumber(&(itup)->t_tid) & GIN_ITUP_COMPRESSED) != 0)

/*
 * 条目树页面上项目的最大大小。确保每个页面至少放置三个项目。
 * （在常规B树索引中，我们必须至少放置三个项目：两个数据项目和“高键”。在GIN条目树中，我们目前不显式存储高键，只使用页面右侧的项目，因此实际上足以放置两个项目。）
 */
#define GinMaxItemSize \
	Min(INDEX_SIZE_MASK, \
		MAXALIGN_DOWN(((BLCKSZ - \
						MAXALIGN(SizeOfPageHeaderData + 3 * sizeof(ItemIdData)) - \
						MAXALIGN(sizeof(GinPageOpaqueData))) / 3)))

/*
 * 非叶条目元组的访问宏
 */
#define GinGetDownlink(itup)	GinItemPointerGetBlockNumber(&(itup)->t_tid)
#define GinSetDownlink(itup,blkno)	ItemPointerSet(&(itup)->t_tid, blkno, InvalidOffsetNumber)


/*
 * 数据（发布树）页面
 *
 * 发布树页面不存储常规元组。非叶页面包含
 * PostingItems，表示ItemPointers和子块编号的对。
 * 叶页面包含GinPostingLists和未压缩的项目指针数组。
 *
 * 在叶页面中，压缩的发布列表存储在常规页面头后面，一个接一个。虽然我们不存储常规元组，
 * pd_lower用于指示发布列表的结束。之后是空闲空间。这种布局与bufpage.h中描述的“标准”堆和
 * 索引页面布局兼容，因此我们可以在写入WAL记录时设置buffer_std。
 *
 * 特殊空间中有GinPageOpaque结构。
 */
#define GinDataLeafPageGetPostingList(page) \
	(GinPostingList *) ((PageGetContents(page) + MAXALIGN(sizeof(ItemPointerData))))
#define GinDataLeafPageGetPostingListSize(page) \
	(((PageHeader) page)->pd_lower - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(ItemPointerData)))

#define GinDataLeafPageIsEmpty(page) \
	(GinPageIsCompressed(page) ? (GinDataLeafPageGetPostingListSize(page) == 0) : (GinPageGetOpaque(page)->maxoff < FirstOffsetNumber))

#define GinDataLeafPageGetFreeSpace(page) PageGetExactFreeSpace(page)

#define GinDataPageGetRightBound(page)	((ItemPointer) PageGetContents(page))
/*
 * 指向发布树页面数据部分的指针。对于内部页面，
 * 这是PostingItems数组的开始。对于压缩的叶页面，
 * 是第一个压缩发布列表。对于未压缩（9.4之前）的叶
 * 页面，这是ItemPointer数组的开始。
 */
#define GinDataPageGetData(page)	\
	(PageGetContents(page) + MAXALIGN(sizeof(ItemPointerData)))
/* 非叶页面包含PostingItems */
#define GinDataPageGetPostingItem(page, i)	\
	((PostingItem *) (GinDataPageGetData(page) + ((i)-1) * sizeof(PostingItem)))

/*
 * 注：没有GinDataPageGetDataSize宏，因为在版本
 * 9.4之前，我们没有在数据页面上设置pd_lower。索引中可能存在从早期版本二进制升级的页面，
 * 仍然具有无效的pd_lower，因此我们一般不能信任它。然而，在9.4中，压缩的发布树叶页面是新的，
 * 因此我们可以信任它们；参见
 * GinDataLeafPageGetPostingListSize。
 */
#define GinDataPageSetDataSize(page, size) \
	{ \
		Assert(size <= GinDataPageMaxDataSize); \
		((PageHeader) page)->pd_lower = (size) + MAXALIGN(SizeOfPageHeaderData) + MAXALIGN(sizeof(ItemPointerData)); \
	}

#define GinNonLeafDataPageGetFreeSpace(page)	\
	(GinDataPageMaxDataSize - \
	 GinPageGetOpaque(page)->maxoff * sizeof(PostingItem))

#define GinDataPageMaxDataSize	\
	(BLCKSZ - MAXALIGN(SizeOfPageHeaderData) \
	 - MAXALIGN(sizeof(ItemPointerData)) \
	 - MAXALIGN(sizeof(GinPageOpaqueData)))

/*
 * 列表页面
 */
#define GinListPageSize  \
	( BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(GinPageOpaqueData)) )

/*
 * 一个压缩的发布列表。
 *
 * 注：这需要2字节对齐。
 */
typedef struct
{
	ItemPointerData first;		/* 该发布列表中的第一个项目（未解包） */
	uint16		nbytes;			/* 随后字节的数量 */
	unsigned char bytes[FLEXIBLE_ARRAY_MEMBER]; /* 变长字节编码的项目 */
} GinPostingList;

#define SizeOfGinPostingList(plist) (offsetof(GinPostingList, bytes) + SHORTALIGN((plist)->nbytes) )
#define GinNextPostingListSegment(cur) ((GinPostingList *) (((char *) (cur)) + SizeOfGinPostingList((cur))))

#endif							/* GINBLOCK_H */
